#ifndef NIGHT_TENSOR_VIEW
#define NIGHT_TENSOR_VIEW

#include "core.hpp"
#include "shape.hpp"
#include "expr.hpp"

namespace night {

namespace tensor {


template< int i > struct call;

template< typename E >
struct scalar_view : Expr< scalar_view<E>, typename E::param_type, shape<> >
{
	typedef typename E::param_type result_type;

	typename E::value_type value;

	scalar_view ( typename E::param_type src ) : value (src) {}

	template< typename Shape >
	result_type at ( Shape const & ) const { return this->value; }
};


template< typename E, typename R, typename V, typename S, int n >
struct base_view : Expr< base_view<E,R,V,S,n>, R, S > 
{
	typedef V this_type;
	typedef R result_type;
	typedef S shape_type;

	typedef typename E::param_type function_type;

	bind_view< E, R, by_ref<this_type>, shape_type, n+1 > operator [] ( size_type k ) {
		return bind_view< E, R, by_ref<this_type>, shape_type, n+1 > ( derived<V> (*this), k );
	}

	template< int k >
	tensor_view< E, R, dimension< E::type::template base<n+1>::value, index<k> >, 
		by_ref<this_type>, shape_type, n+1 > operator [] ( index<k> ) { return derived<V> (*this); }

	template< typename Shape >
	result_type at ( Shape const & s ) const { return call<n>::apply ( derived<V> (this), s ); }
};

template< typename E, typename R, typename I, typename V, typename S, int n >
struct tensor_view : base_view< E, R, tensor_view<E,R,I,V,S,n>, typename join<S,I>::type, n >
{
	typedef I index_type;

	typename V::value_type expr;

	tensor_view ( typename V::param_type src ) : expr (src) {}

	tensor_view & operator = ( tensor_view const & rhs ) { this->assign (rhs); return (*this); }
	template< typename RE >
	tensor_view & operator = ( RE const & rhs ) { this->assign (rhs); return (*this); }

	template< int k, typename Shape >
	size_type get ( index<k>, Shape const & s ) const { return this->expr.get( index<k> (), s ); }
	template< typename Shape >
	size_type get ( index<n>, Shape const & s ) const { return get_index<index_type> (s); }

};

template< typename E, typename R, typename V, typename S, int n >
struct bind_view : base_view< E, R, bind_view<E,R,V,S,n>, S, n >
{
	typename V::value_type expr;
	size_type const value;

	bind_view ( typename V::param_type src, size_type k ) : expr (src), value (k) {}

	bind_view & operator = ( bind_view const & rhs ) { this->assign (rhs); return (*this); }
	template< typename RE >
	bind_view & operator = ( RE const & rhs ) { this->assign (rhs); return (*this); }

	template< int k, typename Shape >
	size_type get ( index<k>, Shape const & s ) const { return this->expr.get( index<k> (), s ); }
	template< typename Shape >
	size_type get ( index<n>, Shape const & s ) const { return this->value; }

};

template<> struct call<0> 
{
	template< typename E >
	static typename E::function_type function ( E const * ptr ) { return ptr->expr; }

	template< typename E, typename Shape >
	static typename E::result_type apply ( E const * ptr, Shape const & s ) {
		return function (ptr) ( ptr->get (_i,s) );
	}
};

template<> struct call<1> 
{
	template< typename E >
	static typename E::function_type function ( E const * ptr ) { return call<0>::function (&ptr->expr); }

	template< typename E, typename Shape >
	static typename E::result_type apply ( E const * ptr, Shape const & s ) {
		return function (ptr) ( ptr->get (_i,s), ptr->get (_j,s) );
	}
};

template<> struct call<2> 
{
	template< typename E >
	static typename E::function_type function ( E const * ptr ) { return call<1>::function (&ptr->expr); }

	template< typename E, typename Shape >
	static typename E::result_type apply ( E const * ptr, Shape const & s ) {
		return function (ptr) ( ptr->get (_i,s), ptr->get (_j,s), ptr->get (_k,s) );
	}
};

} // end namespace night::tensor

} // end namespace nigth

#endif
